#include "TerrainManager.h"
#include "DXMgr.h"
#include "Renderer.h"
#include <fstream>
#include "Util.h" //Debug text - remove later

TerrainManager* Singleton<TerrainManager>::m_pInstance = NULL;

TerrainManager::TerrainManager()
: m_cellSpacing(5)
, m_heightMapFileNames(NULL)
, m_heightMapFile(NULL)
{

}
TerrainManager::~TerrainManager()
{
	for(TerrainVec::iterator iter = m_terrain.begin(); iter != m_terrain.end(); iter++)
	{
		Terrain* pElem = *iter;
		delete pElem;
		pElem = NULL;
	}
	m_terrain.clear();
}
bool TerrainManager::CreateTerrain(char* a_pFileName, int a_numChunks)
{
	long start = GetTickCount();

	m_numChunks = a_numChunks;
	float xOffset;
	float zOffset;

	//Width of the entire terrain chunk
	float terrainWidth;

	//Width and height of chunks
	int height;
	int width;

	//Total verts in the terrain
	float totalVerts;

	//Number of verts per chunk in width
	int numVerts;

	//loop through all chunks and send them an array of heights
	std::ifstream File(a_pFileName, std::ios::binary);
	if(File.is_open())
	{
		File.seekg(0, std::ios::end);

		//Get the width of the terrain
		terrainWidth = sqrt((float)File.tellg());
		//Get the total number of verts
		totalVerts = File.tellg();

		File.seekg(0, std::ios::beg);

		//Calculate the number of verts per chunk
		numVerts = sqrt(totalVerts)/sqrt((float)m_numChunks); 
		//Make sure there is atleast 1 quad per chunk
		assert(numVerts >= 4);

		float* heightMapArray = new float[totalVerts];
		int index = 0;
		//read in the height map file in full
		while(!File.eof())
		{
			heightMapArray[index++] = File.get();
		}

		//calculate the width and height of the grid of chunks
		height = width = sqrt((float)m_numChunks);

		//loop through each chunk and set it up
		for(int h=0; h<height; ++h)
		{
			for(int w=0; w<width; ++w)
			{
				//determine the offset from the origin (0,0) 
				//NOTE: minus 1 from the num verts as the offset is calculated on the number of quads, not verts
				xOffset = w * ((numVerts-1) * m_cellSpacing);
				zOffset = h * ((numVerts-1) * m_cellSpacing);

				//These offsets are used for retrieving the correct data from the terrain data. 
				int zVertOffset = h * numVerts;
				int xVertOffset = w * numVerts;
				//sift through the height map array and set it to the local chunks array
				float* heightData = new float[numVerts*numVerts];
				for(int y=0; y<numVerts; ++y)
				{
					for(int x=0; x<numVerts; ++x)
					{
						heightData[(y*numVerts)+x] = heightMapArray[((y+zVertOffset)*(int)terrainWidth)+(x+xVertOffset)];
					}
				}
				//Create a new terrain chunk with the height data above
				Terrain* pTerrain = new Terrain(m_cellSpacing, heightData, numVerts*numVerts);
				D3DXMATRIX mat;
				D3DXMatrixIdentity(&mat);
				//Position it based on the chunk number
				D3DXMatrixTranslation(&mat, xOffset, 0.0f, zOffset);
				pTerrain->SetWorldMatrix(mat);
				//Add the terrain to the manager
				AddTerrain(pTerrain);
				//Add the terrain to the renderer
				Renderer::GetInstance().AddOctreeObject(pTerrain);
			}
		}
		File.close();

		long end = GetTickCount();

		Util::OutputToDebug("Terrain Creation: %d", end - start); 

		//Make sure there is more than one chunk as theres no point stitching one chunk
		if(m_numChunks > 1) 
		{
			//Smooth the terrain to make it less chunky and more natural
			SmoothTerrain(height, width, numVerts);
			SmoothTerrain(height, width, numVerts);

			//Now that the terrain chunks have been setup, stitch them together
			StitchTerrain(terrainWidth, totalVerts, numVerts, height, width);
		}
	}
	//Couldnt open the file, return false
	else
	{
		return false;
	}
	//Now that the final changes have been made to the heightmap data, create the objects
	for(TerrainVec::iterator iter = m_terrain.begin(); iter != m_terrain.end(); ++iter)
	{
		Terrain* pElem = *iter;	
		//Setup all the terrain chunks
		pElem->CreateObject(DXMgr::GetInstance().GetDevice(), NULL);		
	}
	//CalculateNormals(height, width, numVerts);

	return true;
}
bool TerrainManager::CreateWater(char* a_pFileName, int a_numChunks)
{
	int totalVerts;
	//Width and height of chunks
	int height;
	int width;

	D3DXVECTOR3* newVerts = new D3DXVECTOR3[10000];

	std::ifstream File(a_pFileName, std::ios::binary);
	if(File.is_open())
	{
		File.seekg(0, std::ios::end);

		totalVerts = File.tellg();

		File.seekg(0, std::ios::beg);

		float* heightMapArray = new float[totalVerts];

		int index = 0;
		//read in the height map file in full
		while(!File.eof())
		{
			heightMapArray[index++] = File.get();
		}

		//calculate the width and height of the grid of chunks
		height = width = sqrt((float)totalVerts);

		index = 0;
		for(int y=0; y<height; ++y)
		{
			for(int x=0; x<width; ++x)
			{
				if(heightMapArray[(y*height)+x] == 255)
				{
					//create a vert for this
					newVerts[index++] = D3DXVECTOR3(x, 10, y);
				}
			}
		}
	}
	//Couldnt open the file, return false
	else
	{
		return false;
	}

	return true;
}
void TerrainManager::CalculateNormals(int a_height, int a_width, int a_numVerts)
{
	D3DXVECTOR3 up, down, left, right;

	//loop through each chunk
	for(int h=0; h<a_height; ++h)
	{
		for(int w=0; w<a_width; ++w)
		{
			//loop through all the verts
			for(int y=0; y<a_numVerts; ++y)
			{
				for(int x=0; x<a_numVerts; ++x)
				{
					if(y != 0)
					{
						//sample below
						down = m_terrain.at((h*a_height)+w)->GetVertexData(((y-1)*a_numVerts)+x).pos;
						down -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
						D3DXVec3Normalize(&down, &down);
					}
					else
					{
						//sample chunk below
						if(h != 0)
						{
							down = m_terrain.at(((h-1)*a_height)+w)->GetVertexData(((a_numVerts-1)*a_numVerts)+x).pos;
							down -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
							D3DXVec3Normalize(&down, &down);
						}
					}

					if(y != a_numVerts-1)
					{
						//sample above
						up = m_terrain.at((h*a_height)+w)->GetVertexData(((y+1)*a_numVerts)+x).pos;
						up -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
						D3DXVec3Normalize(&up, &up);
					}
					else
					{
						//sample chunk above
						if(h != a_height-1)
						{
							up = m_terrain.at(((h+1)*a_height)+w)->GetVertexData((0*a_numVerts)+x).pos;
							up -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
							D3DXVec3Normalize(&up, &up);
						}
					}

					if(x != 0)
					{
						//sample left
						left = m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+(x-1)).pos;
						left -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
						D3DXVec3Normalize(&left, &left);
					}
					else
					{
						//sample chunk left
						if(w != 0)
						{
							left = m_terrain.at((h*a_height)+(w-1))->GetVertexData((y*a_numVerts)+(a_numVerts-1)).pos;
							left -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
							D3DXVec3Normalize(&left, &left);
						}
					}

					if(x != a_numVerts-1)
					{
						//sample right
						right = m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+(x+1)).pos;
						right -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
						D3DXVec3Normalize(&right, &right);
					}
					else
					{
						//sample chunk right
						if(w != a_width-1)
						{
							right = m_terrain.at((h*a_height)+(w+1))->GetVertexData((y*a_numVerts)+0).pos;
							right -= m_terrain.at((h*a_height)+w)->GetVertexData((y*a_numVerts)+x).pos;
							D3DXVec3Normalize(&right, &right);
						}
					}
					//cross products
					//up and left
					D3DXVECTOR3 temp1;
					D3DXVECTOR3 temp2;
					D3DXVec3Cross(&temp1, &down, &left);

					//cross products
					//down and right
					D3DXVec3Cross(&temp2, &up, &right);

					temp1 += temp2;

					//Normalize the new vector to give a unit length directional vector
					D3DXVec3Normalize(&temp1, &temp1);
					//Apply the normal
					m_terrain.at((h*a_height)+w)->SetVertexNormal((y*a_numVerts)+x, temp1);
				}
			}
		}
	}
}
void TerrainManager::SmoothTerrain(int a_height, int a_width, int a_numVerts)
{
	long start = GetTickCount();

	//loop through each chunk
	for(int h=0; h<a_height; ++h)
	{
		for(int w=0; w<a_width; ++w)
		{
			//loop through all the verts
			for(int y=0; y<a_numVerts; ++y)
			{
				for(int x=0; x<a_numVerts; ++x)
				{
					int avg;
					int avgCounter = 0;
					float up, down, left, right;
					up=down=left=right = 0;

					//Include the current vert in the average calculation
					float currentVert = m_terrain.at((h*a_height)+w)->GetHeight((y*a_numVerts)+x);
					++avgCounter;

					if(y != 0)
					{
						//sample below
						down = m_terrain.at((h*a_height)+w)->GetHeight(((y-1)*a_numVerts)+x);
						++avgCounter;
					}
					else
					{
						//sample chunk below
						if(h != 0)
						{
							down = m_terrain.at(((h-1)*a_height)+w)->GetHeight(((a_numVerts-1)*a_numVerts)+x);
							++avgCounter;
						}
					}

					if(y != a_numVerts-1)
					{
						//sample above
						up = m_terrain.at((h*a_height)+w)->GetHeight(((y+1)*a_numVerts)+x);
						++avgCounter;
					}
					else
					{
						//sample chunk above
						if(h != a_height-1)
						{
							up = m_terrain.at(((h+1)*a_height)+w)->GetHeight((0*a_numVerts)+x);
							++avgCounter;
						}
					}

					if(x != 0)
					{
						//sample left
						left = m_terrain.at((h*a_height)+w)->GetHeight((y*a_numVerts)+(x-1));
						++avgCounter;
					}
					else
					{
						//sample chunk left
						if(w != 0)
						{
							left = m_terrain.at((h*a_height)+(w-1))->GetHeight((y*a_numVerts)+(a_numVerts-1));
							++avgCounter;
						}
					}

					if(x != a_numVerts-1)
					{
						//sample right
						right = m_terrain.at((h*a_height)+w)->GetHeight((y*a_numVerts)+(x+1));
						++avgCounter;
					}
					else
					{
						//sample chunk right
						if(w != a_width-1)
						{
							right = m_terrain.at((h*a_height)+(w+1))->GetHeight((y*a_numVerts)+0);
							++avgCounter;
						}
					}

					//Only take into account samples taken, if one direction was not able to be sampled, than the value will be
					//zero, and the avgCounter will not take that direction into account, giving an accurate average.
					avg = (up + down + left + right + currentVert)/avgCounter;

					//Apply the average height to the vert
					m_terrain.at((h*a_height)+w)->SetHeight((y*a_numVerts)+x, avg);
				}
			}
			//the average value should maybe be scaled by the multiplier?? just needs to be more smoothed as the multiplier increases
		}
	}
	long end = GetTickCount();

	Util::OutputToDebug("Terrain Smoothing: %d", end - start); 
}
void TerrainManager::StitchTerrain(int a_terrainWidth, int a_totalVerts, int a_numVerts, int a_height, int a_width)
{
	long start = GetTickCount();

	//loop through chunks
	for(int h=0; h<a_height; ++h)
	{
		for(int w=0; w<a_width; ++w)
		{
			//If theres a valid chunk to the bottom
			if(w != 0)
			{
				//loop through all the verts down one side
				for(int i=0; i<a_numVerts; ++i)
				{
					//First corner - Only need to do the left corner except for the first column as this will stop duplicate stitching
					if(i == 0)
					{
						//If theres a valid chunk to the left
						if(h != 0)
						{
							//sample 3 chunks
							//top right - needs bottom left vert
							int bottomSide = m_terrain.at((h*a_height)+w)->GetHeight((0*a_numVerts)+0);
							//bottom right - needs top left vert
							int matchingOne = m_terrain.at((h*a_height)+(w-1))->GetHeight((0*a_numVerts)+(a_numVerts-1));
							//top left - needs bottom right vert
							int matchingTwo = m_terrain.at(((h-1)*a_height)+w)->GetHeight(((a_numVerts-1)*a_numVerts)+0);
							//bottom left - needs top right vert
							int matchingThree = m_terrain.at(((h-1)*a_height)+(w-1))->GetHeight(((a_numVerts-1)*a_numVerts)+(a_numVerts-1));

							//Calculate the average height
							int avg = (bottomSide + matchingOne + matchingTwo + matchingThree)/4;

							//Set the average height to all the corners
							m_terrain.at((h*a_height)+w)->SetHeight((0*a_numVerts)+0, avg);
							m_terrain.at((h*a_height)+(w-1))->SetHeight((0*a_numVerts)+(a_numVerts-1), avg);
							m_terrain.at(((h-1)*a_height)+w)->SetHeight(((a_numVerts-1)*a_numVerts)+0, avg);
							m_terrain.at(((h-1)*a_height)+(w-1))->SetHeight(((a_numVerts-1)*a_numVerts)+(a_numVerts-1), avg);
						}
						else
						{
							//if i == 0 but h == 0, then standard stitching will need to be performed: 
							int bottomSide = m_terrain.at((h*a_height)+w)->GetHeight((i*a_numVerts)+0);
							int matchingBottom = m_terrain.at((h*a_height)+(w-1))->GetHeight((i*a_numVerts)+a_numVerts-1);

							int avg = (bottomSide+matchingBottom)/2;

							m_terrain.at((h*a_height)+w)->SetHeight((i*a_numVerts)+0, avg);
							m_terrain.at((h*a_height)+(w-1))->SetHeight((i*a_numVerts)+a_numVerts-1, avg);
						}
					}
					//Not a corner (and not the last corner) - only sample 1
					else
					{
						int bottomSide = m_terrain.at((h*a_height)+w)->GetHeight((i*a_numVerts)+0);
						int matchingBottom = m_terrain.at((h*a_height)+(w-1))->GetHeight((i*a_numVerts)+a_numVerts-1);

						//Calculate average height
						int avg = (bottomSide+matchingBottom)/2;

						//Set the average height to the verts
						m_terrain.at((h*a_height)+w)->SetHeight((i*a_numVerts)+0, avg);
						m_terrain.at((h*a_height)+(w-1))->SetHeight((i*a_numVerts)+a_numVerts-1, avg);
					}
				}
			}
			//if theres a chunk to the left
			if(h != 0)
			{
				//loop through all the verts down one side
				for(int i=0; i<a_numVerts; ++i)
				{
					int leftSide = m_terrain.at((h*a_height)+w)->GetHeight((0*a_numVerts)+i);
					int matchingLeft = m_terrain.at(((h-1)*a_height)+w)->GetHeight(((a_numVerts-1)*a_numVerts)+i);

					//Calculate average height
					int avg = (leftSide+matchingLeft)/2;

					//Set the average height to the verts
					m_terrain.at((h*a_height)+w)->SetHeight((0*a_numVerts)+i, avg);
					m_terrain.at(((h-1)*a_height)+w)->SetHeight(((a_numVerts-1)*a_numVerts)+i, avg);
				}
			}
		}
	}
	long end = GetTickCount();

	Util::OutputToDebug("Terrain Stitching: %d", end - start); 
}
float TerrainManager::GetTerrainHeight(float a_xPos, float a_zPos)
{
	//Check which terrain object the player is on
	for(TerrainVec::iterator iter = m_terrain.begin(); iter != m_terrain.end(); iter++)
	{
		Terrain* pElem = *iter;
		D3DXMATRIX matPos = pElem->GetWorldMatrix();
		D3DXVECTOR3 bottomLeft(matPos._41, matPos._42, matPos._43);
		D3DXVECTOR3 topRight(matPos._41 + pElem->GetTerrainWidth(), matPos._42 + pElem->GetTerrainWidth(), matPos._43 + pElem->GetTerrainWidth());

		//If the position is within this terrain chunk, return the height calculated
		if(a_xPos > bottomLeft.x && a_xPos < topRight.x)
		{
			if(a_zPos > bottomLeft.z && a_zPos < topRight.z)
			{
				Terrain* pTerrain = pElem;
				//Get the height at the passed in position
				return pTerrain->GetHeightAtPos(a_xPos, a_zPos);
			}
		}
	}
	//Didnt have any terrain stored or none was found with your current coords
	return 0;	
}
void TerrainManager::Pick()
{
	//Pick the terrain
	/*for(TerrainList::iterator iter = m_terrain.begin(); iter != m_terrain.end(); ++iter)
	{
	Terrain* pElem = *iter;
	if(Util::RayPick(pElem->GetMesh(), pElem->GetWorldMatrix(), a_outDist))
	{
	int one = 1;
	//Get collided tri index, calculate the quad based on if its odd or even (modulate the number by 2)
	//Then somehow change the colour of the 2 triangles with those indices
	}
	}*/
}
bool TerrainManager::AddTerrain(Terrain* a_pTerrain)
{
	if (a_pTerrain)
	{
		bool isFound = false;
		for (TerrainVec::iterator iter = m_terrain.begin(); iter != m_terrain.end(); ++iter)
		{
			Terrain* pElem = *iter;
			if (pElem == a_pTerrain)
			{
				// Found the terrain!
				isFound = true;
				break;
			}
		}

		if (!isFound)
		{
			// Terrain was not already in list - add it
			m_terrain.push_back(a_pTerrain);
		}
		return true;
	}

	return false;  // Invalid parameters
}
void TerrainManager::AddHeightMap(char* a_pFileName)
{
	//This will eventually get replaced with one large map
	m_heightMapFileNames.push_back(a_pFileName);
}
void TerrainManager::AddSingleHeightMap(char* a_pFileName)
{
	m_heightMapFile = a_pFileName;
}